#pragma once

#include <unordered_map>

#include "object/unit.h"
#include "metadata/item.h"

#define MAX_MONEY_AMOUNT        (0x7FFFFFFF-1)
#define PLAYER_MAX_SKILLS           127
#define PLAYER_EXPLORED_ZONES_SIZE  64

namespace cftf::mysql
{
	class DataSet;
}

enum PlayerSlots
{
	// first slot for item stored (in any way in player m_items data)
	PLAYER_SLOT_START = 0,
	// last+1 slot for item stored (in any way in player m_items data)
	PLAYER_SLOT_END = 118,
	PLAYER_SLOTS_COUNT = (PLAYER_SLOT_END - PLAYER_SLOT_START)
};

#define INVENTORY_SLOT_BAG_0    255

enum EquipmentSlots                                         // 19 slots
{
	EQUIPMENT_SLOT_START = 0,
	EQUIPMENT_SLOT_HEAD = 0,
	EQUIPMENT_SLOT_NECK = 1,
	EQUIPMENT_SLOT_SHOULDERS = 2,
	EQUIPMENT_SLOT_BODY = 3,
	EQUIPMENT_SLOT_CHEST = 4,
	EQUIPMENT_SLOT_WAIST = 5,
	EQUIPMENT_SLOT_LEGS = 6,
	EQUIPMENT_SLOT_FEET = 7,
	EQUIPMENT_SLOT_WRISTS = 8,
	EQUIPMENT_SLOT_HANDS = 9,
	EQUIPMENT_SLOT_FINGER1 = 10,
	EQUIPMENT_SLOT_FINGER2 = 11,
	EQUIPMENT_SLOT_TRINKET1 = 12,
	EQUIPMENT_SLOT_TRINKET2 = 13,
	EQUIPMENT_SLOT_BACK = 14,
	EQUIPMENT_SLOT_MAINHAND = 15,
	EQUIPMENT_SLOT_OFFHAND = 16,
	EQUIPMENT_SLOT_RANGED = 17,
	EQUIPMENT_SLOT_TABARD = 18,
	EQUIPMENT_SLOT_END = 19
};

#define NEGATIVE_HONOR_RANK_COUNT 4
#define POSITIVE_HONOR_RANK_COUNT 15
#define HONOR_RANK_COUNT 19 // negative + positive ranks

struct HonorRankInfo
{
	unsigned char rank;      ///< Internal range [0..18]
	char visualRank; ///< Number visualized in rank bar [-4..14] 14 being High Warlord, -4 being Pariah)
	float maxRP;
	float minRP;
	bool positive;
};

struct HonorCP
{
	unsigned char victimType;
	unsigned int victimID;
	float honorPoints;
	unsigned int date;
	unsigned char type;
	unsigned char state;
	bool isKill;
};

typedef std::list<HonorCP> HonorCPMap;

/**
 * @brief handle the queue types and bg types separately to enable joining queue for different sized arenas at the same time
 *
 */
enum BattleGroundQueueTypeId
{
	BATTLEGROUND_QUEUE_NONE = 0,
	BATTLEGROUND_QUEUE_AV = 1,
	BATTLEGROUND_QUEUE_WS = 2,
	BATTLEGROUND_QUEUE_AB = 3,
};
#define MAX_BATTLEGROUND_QUEUE_TYPES 4

#define PLAYER_MAX_BATTLEGROUND_QUEUES 3

// indexes of BattlemasterList.dbc
enum BattleGroundTypeId
{
	BATTLEGROUND_TYPE_NONE = 0,
	BATTLEGROUND_AV = 1,
	BATTLEGROUND_WS = 2,
	BATTLEGROUND_AB = 3,
};

#define MAX_BATTLEGROUND_TYPE_ID 4

// In fact !=0 values is alliance/horde root faction ids
enum Team
{
	TEAM_NONE = 0,                                // used when team value unknown or not set, 0 is also meaning that can be used !team check
	TEAM_BOTH_ALLOWED = 0,                                // used when a check should evaluate true for both teams
	TEAM_INVALID = 1,                                // used to invalidate some team depending checks (means not for both teams)
	HORDE = 67,
	ALLIANCE = 469,
};

enum DrunkenState
{
	DRUNKEN_SOBER = 0,
	DRUNKEN_TIPSY = 1,
	DRUNKEN_DRUNK = 2,
	DRUNKEN_SMASHED = 3
};

#define MAX_DRUNKEN             4

enum PlayedTimeIndex
{
	PLAYED_TIME_TOTAL = 0,
	PLAYED_TIME_LEVEL = 1
};

#define MAX_PLAYED_TIME_INDEX   2

enum PlayerFlags
{
	PLAYER_FLAGS_NONE = 0x00000000,
	PLAYER_FLAGS_GROUP_LEADER = 0x00000001,
	PLAYER_FLAGS_AFK = 0x00000002,
	PLAYER_FLAGS_DND = 0x00000004,
	PLAYER_FLAGS_GM = 0x00000008,
	PLAYER_FLAGS_GHOST = 0x00000010,
	PLAYER_FLAGS_RESTING = 0x00000020,
	PLAYER_FLAGS_UNK7 = 0x00000040,       // admin?
	PLAYER_FLAGS_FFA_PVP = 0x00000080,
	PLAYER_FLAGS_CONTESTED_PVP = 0x00000100,       // Player has been involved in a PvP combat and will be attacked by contested guards
	PLAYER_FLAGS_IN_PVP = 0x00000200,
	PLAYER_FLAGS_HIDE_HELM = 0x00000400,
	PLAYER_FLAGS_HIDE_CLOAK = 0x00000800,
	PLAYER_FLAGS_PARTIAL_PLAY_TIME = 0x00001000,       // played long time
	PLAYER_FLAGS_NO_PLAY_TIME = 0x00002000,       // played too long time
	PLAYER_FLAGS_UNK15 = 0x00004000,
	PLAYER_FLAGS_UNK16 = 0x00008000,       // strange visual effect (2.0.1), looks like PLAYER_FLAGS_GHOST flag
	PLAYER_FLAGS_SANCTUARY = 0x00010000,       // player entered sanctuary
	PLAYER_FLAGS_TAXI_BENCHMARK = 0x00020000,       // taxi benchmark mode (on/off) (2.0.1)
	PLAYER_FLAGS_PVP_TIMER = 0x00040000,       // 3.0.2, pvp timer active (after you disable pvp manually)
	PLAYER_FLAGS_XP_USER_DISABLED = 0x02000000,
};

#define TaxiMaskSize 8
typedef unsigned int TaxiMask[TaxiMaskSize];

enum InventorySlots                                         // 4 slots
{
	INVENTORY_SLOT_BAG_START = 19,
	INVENTORY_SLOT_BAG_END = 23
};

enum SkillUpdateState
{
	SKILL_UNCHANGED = 0,
	SKILL_CHANGED = 1,
	SKILL_NEW = 2,
	SKILL_DELETED = 3
};

struct SkillStatusData
{
	SkillStatusData(unsigned char _pos, SkillUpdateState _uState) : pos(_pos), uState(_uState)
	{
	}
	unsigned char pos = 0;
	SkillUpdateState uState;
};

enum BankBagSlots                                           // 7 slots
{
	BANK_SLOT_BAG_START = 63,
	BANK_SLOT_BAG_END = 69
};

enum BuyBackSlots                                           // 12 slots
{
	// stored in m_buybackitems
	BUYBACK_SLOT_START = 69,
	BUYBACK_SLOT_END = 81
};

enum KeyRingSlots                                           // 32 slots
{
	KEYRING_SLOT_START = 81,
	KEYRING_SLOT_END = 97
};

class Map;
class UpdateData;
class DungeonPersistentState;
namespace battle::object
{

	/// Holder for BattleGround data
	struct BGData
	{
		BGData() : bgInstanceID(0), bgTypeID(BATTLEGROUND_TYPE_NONE), bgAfkReportedCount(0), bgAfkReportedTimer(0),
			bgTeam(TEAM_NONE), m_needSave(false) {
		}

		unsigned int bgInstanceID;                                    ///< This variable is set to bg->m_InstanceID, saved
		///  when player is teleported to BG - (it is battleground's GUID)
		BattleGroundTypeId bgTypeID;

		std::set<unsigned int>   bgAfkReporter;
		unsigned char              bgAfkReportedCount;
		time_t             bgAfkReportedTimer;

		Team bgTeam;                                            ///< What side the player will be added to, saved

		WorldLocation joinPos;                                  ///< From where player entered BG, saved

		bool m_needSave;                                        ///< true, if saved to DB fields modified after prev. save (marked as "saved" above)
	};

	class PlayerTaxi
	{
	public:
		PlayerTaxi();
		~PlayerTaxi() {}
		// Nodes
		void InitTaxiNodes(unsigned int race, unsigned int level);
		void LoadTaxiMask(const char* data);

		bool IsTaximaskNodeKnown(unsigned int nodeidx) const
		{
			unsigned char  field = unsigned char((nodeidx - 1) / 32);
			unsigned int submask = 1 << ((nodeidx - 1) % 32);
			return (m_taximask[field] & submask) == submask;
		}
		bool SetTaximaskNode(unsigned int nodeidx)
		{
			unsigned char  field = unsigned char((nodeidx - 1) / 32);
			unsigned int submask = 1 << ((nodeidx - 1) % 32);
			if ((m_taximask[field] & submask) != submask)
			{
				m_taximask[field] |= submask;
				return true;
			}
			else
			{
				return false;
			}
		}
#if 0
		void AppendTaximaskTo(ByteBuffer& data, bool all);
#endif

		// Destinations
		bool LoadTaxiDestinationsFromString(const std::string& values, Team team);
		std::string SaveTaxiDestinationsToString();

		void ClearTaxiDestinations()
		{
			m_TaxiDestinations.clear();
		}
		void AddTaxiDestination(unsigned int dest)
		{
			m_TaxiDestinations.push_back(dest);
		}
		unsigned int GetTaxiSource() const
		{
			return m_TaxiDestinations.empty() ? 0 : m_TaxiDestinations.front();
		}
		unsigned int GetTaxiDestination() const
		{
			return m_TaxiDestinations.size() < 2 ? 0 : m_TaxiDestinations[1];
		}
		unsigned int GetCurrentTaxiPath() const;
		unsigned int NextTaxiDestination()
		{
			m_TaxiDestinations.pop_front();
			return GetTaxiDestination();
		}
		bool empty() const
		{
			return m_TaxiDestinations.empty();
		}

		friend std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi);
	private:
		TaxiMask m_taximask;
		std::deque<unsigned int> m_TaxiDestinations;
	};

	class Item;
	class Player : public battle::object::Unit
	{
	public:
		enum { HID = HID_e::kPlayer };
	public:
		Player();
		DungeonPersistentState* GetBoundInstanceSaveForSelfOrGroup(unsigned int mapid);
		unsigned int GetBattleGroundId()  const { return 0; }
		bool isGameMaster() const { return false; }
		virtual bool CanSwim() const override { return true; }
		virtual bool CanFly() const override { return false; }
		bool IsFlying() const { return false; }
		bool IsFreeFlying() const { return false; }
		void UnbindInstance(unsigned int mapid, bool unload) {}

		cftc::util::ListEntry<Player*>* GetPlayerMapEntry() { return player_map_entry_.get(); }
		bool IsInMap(const WorldObject* obj) const
		{
			return IsInWorld() && obj->IsInWorld() && (GetMap() == obj->GetMap());
		}
		bool HaveAtClient(WorldObject const* u) { return true; }
		void UpdateVisibilityOf(WorldObject const* viewPoint, WorldObject* target) {};
		void UpdateVisibilityOf(WorldObject const* viewPoint, WorldObject* target, UpdateData& data, std::set<WorldObject*>& visibleNow) {};
		bool LoadFromDB(ObjectGuid guid, const std::vector<std::shared_ptr<cftf::mysql::DataSet>>& datasets);

		void SetFreePrimaryProfessions(unsigned short profs)
		{
			SetUint32Value(PLAYER_CHARACTER_POINTS2, profs);
		}
		void SetMoney(unsigned int value)
		{
			SetUint32Value(PLAYER_FIELD_COINAGE, value);
			MoneyChanged(value);
		}
		void SendCorpseReclaimDelay(bool load = false);
		void SendInitialPacketsBeforeAddToMap();
		void SendCinematicStart(unsigned int CinematicSequenceId);
		unsigned int getCinematic()
		{
			return m_cinematic;
		}
		void setCinematic(unsigned int cine)
		{
			m_cinematic = cine;
		}
		template<typename T>
		void SendDirectMessage(std::shared_ptr<T> msg)
		{
		}
		AreaLockStatus GetAreaTriggerLockStatus(mt::AreaTrigger const* at, unsigned int& miscRequirement);
		bool TeleportTo(unsigned int mapid, float x, float y, float z, float orientation, unsigned int options = 0, bool allowNoDelay = false);
		bool TeleportToHomebind(unsigned int options = 0) { return TeleportTo(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, GetOrientation(), options); }
		void SetResistanceBuffMods(SpellSchools school, bool positive, float val) { SetFloatValue(positive ? PLAYER_FIELD_RESISTANCEBUFFMODSPOSITIVE + school : PLAYER_FIELD_RESISTANCEBUFFMODSNEGATIVE + school, val); }
	protected:
		HonorCPMap m_honorCP{0};
		HonorRankInfo m_honor_rank{0};
		HonorRankInfo m_highest_rank{0};
		float m_stored_honor = 0;
		unsigned int m_standing_pos = 0;
		unsigned int m_stored_dishonorableKills = 0;
		unsigned int m_stored_honorableKills = 0;
		void RelocateToHomebind() { SetLocationMapId(m_homebindMapId); Relocate(m_homebindX, m_homebindY, m_homebindZ); }
		// in 0.12 and later in Unit
		void InitStatBuffMods()
		{
			for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
			{
				SetFloatValue(PLAYER_FIELD_POSSTAT0 + i, 0);
			}
			for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
			{
				SetFloatValue(PLAYER_FIELD_NEGSTAT0 + i, 0);
			}
		}
		void InitDataForForm(bool reapplyMods);
		void SetRegularAttackTime();
		Item* GetWeaponForAttack(WeaponAttackType attackType, bool nonbroken, bool useable) const;
		Item* GetItemByPos(unsigned char bag, unsigned char slot) const;
		void UpdateEquipSpellsAtFormChange();
		void ApplyItemEquipSpell(Item* item, bool apply, bool form_change);
		void ApplyEquipSpell(dbc::SpellEntry const* spellInfo, Item* item, bool apply, bool form_change);
	private:
		void SetVisibleItemSlot(unsigned int slot, Item* pItem);
		void setFactionForRace(unsigned char race);
		unsigned int getFactionForRace(unsigned char race);
		void InitPrimaryProfessions();
		void _LoadHonorCP(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadBoundInstances(std::shared_ptr<cftf::mysql::DataSet> ds);
		bool IsPositionValid() const;
		void _LoadIntoDataField(const char* data, unsigned int startOffset, unsigned int count);
		void InitDisplayIds();
		void MoneyChanged(unsigned int count);
		void outDebugStatsValues() const;
		bool _LoadHomeBind(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadBGData(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadSpellCooldowns(std::shared_ptr<cftf::mysql::DataSet> ds);
		bool UpdateAllStats();
#if 0
		void SetDrunkValue(unsigned short newDrunkenValue, unsigned int /*itemId*/);
#endif
		DrunkenState GetDrunkenstateByValue(unsigned short value);
		void SetDrunkValue(unsigned short newDrunkValue, unsigned int itemid = 0);
		void InitStatsForLevel(bool reapplyMods = false);
		float GetRestBonus() const
		{
			return m_rest_bonus;
		}
		void SetRestBonus(float rest_bonus_new);
		float ComputeRest(time_t timePassed, bool offline = false, bool inRestPlace = false);
		void _LoadSkills(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadMails(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadMailedItems(std::shared_ptr<cftf::mysql::DataSet> ds);
		void UpdateNextMailTimeAndUnreads();
		void _LoadAuras(std::shared_ptr<cftf::mysql::DataSet> ds, unsigned int timediff);
		void _LoadSpells(std::shared_ptr<cftf::mysql::DataSet> ds);
		void InitTalentForLevel();
		void UpdateFreeTalentPoints(bool resetIfNeed = true);
		void learnDefaultSpells();
		void _LoadQuestStatus(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadInventory(std::shared_ptr<cftf::mysql::DataSet> ds, unsigned int timediff);
		void _LoadItemLoot(std::shared_ptr<cftf::mysql::DataSet> ds);
		void _LoadActions(std::shared_ptr<cftf::mysql::DataSet> ds);
		void UpdateItemDuration(unsigned int time, bool realtimeonly = false);
		void SetFallInformation(unsigned int time, float z)
		{
			m_lastFallTime = time;
			m_lastFallZ = z;
		}
		// Recall position
		unsigned int m_recallMap;
		float  m_recallX;
		float  m_recallY;
		float  m_recallZ;
		float  m_recallO;
		void   SaveRecallPosition();

		void SetCreatedDate(unsigned int createdDate)
		{
			m_created_date = createdDate;
		}
		void _RemoveAllStatBonuses();
		void _RemoveAllItemMods();
		void UpdateSkillsForLevel();
		void UpdateAttackPowerAndDamage(bool ranged);
	private:
		unsigned short m_drunk = 0;
		Item* m_items[PLAYER_SLOTS_COUNT] = {nullptr};
		std::shared_ptr<cftc::util::ListEntry<Player*>> player_map_entry_;

		// Homebind coordinates
		unsigned int m_homebindMapId = 0;
		unsigned short m_homebindAreaId = 0;
		float m_homebindX = 0.0f;
		float m_homebindY = 0.0f;
		float m_homebindZ = 0.0f;

		struct BgBattleGroundQueueID_Rec
		{
			BattleGroundQueueTypeId bgQueueTypeId;
			unsigned int invitedToInstance;
		};
		BgBattleGroundQueueID_Rec m_bgBattleGroundQueueID[PLAYER_MAX_BATTLEGROUND_QUEUES];
		BGData                    m_bgData;

		Transport* m_transport = nullptr;
		unsigned int m_cinematic = 0;
		unsigned int m_Played_time[MAX_PLAYED_TIME_INDEX] = {0};

		unsigned int m_resetTalentsCost = 0;
		time_t m_resetTalentsTime = 0;
		unsigned int m_usedTalentCount = 0;

		PlayerTaxi m_taxi;
		unsigned int m_stableSlots = 0;
		unsigned int m_atLoginFlags = 0;
		time_t m_deathExpireTime = 0;

		float m_rest_bonus = 0.0f;

		unsigned int m_lastFallTime = 0;
		float  m_lastFallZ = 0.0f;

		unsigned int m_created_date = 0;

		std::unordered_map<unsigned int, SkillStatusData> mSkillStatus;
		float m_SpellCritPercentage[MAX_SPELL_SCHOOL] = {0.0f};
		std::vector<const mt::Item::ItemSetEffect*> ItemSetEff;
	};

}
